package test;

import static javax.xml.XMLConstants.W3C_XML_SCHEMA_NS_URI;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBElement;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.bind.ValidationEvent;
import javax.xml.bind.ValidationEventHandler;
import javax.xml.bind.ValidationEventLocator;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import utils.PropertyLoader;
import utils.StringUtils;
import edu.idei.mydat.SnippetType;
import edu.idei.mydat.Template;

@SuppressWarnings("unused")
public class TemplateApp {

	public TemplateApp() {
		compileTemplate("jpa.entity");
	}

	private static List<String> compiledTemplates = new ArrayList<String>();

	private boolean isCompiled(String template) {
		return compiledTemplates.contains(template);
	}

	private void compileTemplate(String template) {
		if (isCompiled(template)) {
			return;
		}
		String templateDocument = "templates/" + template + ".mydat.xml";
		try {
			JAXBElement<Template> te = read(templateDocument);
			Template t = te.getValue();
			String className = StringUtils.templateNameToClassName(template);
			File destFile = new File("src/generator/" + className
					+ "TemplateClass.java");
			StringBuilder sb = new StringBuilder();
			sb.append(classBeginning(t));
			sb.append(isProcessableMethod(t));
			sb.append(createFileMethod(t));
			sb.append(elementSetter(t));
			sb.append(returningMethod(t));
			sb.append(definedMethods(t));
			sb.append("\n}");
			PrintWriter pw = new PrintWriter(new FileOutputStream(destFile));
			pw.print(sb.toString());
			pw.close();
			compiledTemplates.add(template);
			System.out.println("Template '" + template
					+ "' was compiled successfully.");
			for (String tn : dependencies(t)) {
				compileTemplate(tn);
			}
		} catch (FileNotFoundException fnfe) {
			System.err.println("Template '" + template + "' not found.");
		} catch (JAXBException jbe) {
			System.out.println("Parsing problem: " + jbe.getMessage());
		}
	}

	// This sample application demonstrates how to modify a java content
	// tree and marshal it back to a xml data
	@SuppressWarnings("unchecked")
	private static JAXBElement<Template> read(String templateFile)
			throws JAXBException, FileNotFoundException {
		// create a JAXBContext capable of handling classes generated into
		// the primer.po package
		JAXBContext jc = JAXBContext.newInstance("edu.idei.mydat");

		// create an Unmarshaller
		Unmarshaller u = jc.createUnmarshaller();

		SchemaFactory sf = SchemaFactory.newInstance(W3C_XML_SCHEMA_NS_URI);

		try {
			Schema schema = sf.newSchema(new File("mydat-v0.1.xsd"));
			u.setSchema(schema);
			u.setEventHandler(new ValidationEventHandler() {
				// allow unmarshalling to continue even if there are errors
				public boolean handleEvent(ValidationEvent ve) {
					// ignore warnings
					if (ve.getSeverity() != ValidationEvent.WARNING) {
						ValidationEventLocator vel = ve.getLocator();
						System.out.println("Line:Col[" + vel.getLineNumber()
								+ ":" + vel.getColumnNumber() + "]:"
								+ ve.getMessage());
					}

					return true;
				}
			});
		} catch (org.xml.sax.SAXException se) {
			System.out.println("Unable to validate due to following error.");
			se.printStackTrace();
		}

		// unmarshal a po instance document into a tree of Java content
		// objects composed of classes from the primer.po package.
		JAXBElement<Template> tempe = (JAXBElement<Template>) u
				.unmarshal(new FileInputStream(templateFile));
		return tempe;
	}

	private static void save(JAXBElement<Template> t, String templateFile)
			throws JAXBException, FileNotFoundException {
		// create a JAXBContext capable of handling classes generated into
		// the primer.po package
		JAXBContext jc = JAXBContext.newInstance("edu.idei.mydat");
		// create a Marshaller and marshal to a file
		Marshaller m = jc.createMarshaller();
		m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, Boolean.TRUE);

		PrintWriter pw = new PrintWriter(new FileOutputStream(templateFile));
		m.marshal(t, pw);
		pw.close();
	}

	public static void main(String[] args) throws FileNotFoundException,
			JAXBException {
		new TemplateApp();
		// ObjectFactory factory = new ObjectFactory();
		// JAXBElement<Template> te =
		// factory.createTemplate(factory.createTemplate());
		// Template t = te.getValue();
		// t.setFile("/home/user/pepe.xml");
		// t.setInherits("hoguito.com");
		// t.setName("jpa.entity");
		// t.setPrecondition("isValid()");
		// t.setReturning("generateCode()");
		// t.setSnippet("Aquí va el código que debería generar");
		// t.setJavaCode("Aquí iría el código java");

		// save(te, "template_sample_1.xml");
	}

	private String classBeginning(Template t) {
		StringBuilder sb = new StringBuilder();
		String className = StringUtils.templateNameToClassName(t.getName())
				+ "TemplateClass";
		String superClassName = StringUtils.templateNameToClassName(t
				.getExtends()) + "TemplateClass";
		sb.append("package generator;\n");
		sb.append("\n");
		sb.append("import org.eclipse.uml2.uml.Element;\n");
		sb.append("import java.util.Properties;\n");
		sb.append("\n");
		if (t.isAbstract()) {
			sb.append("public abstract class ");
		} else {
			sb.append("public class ");
		}
		sb.append(className);
		if (!t.getName().equals("base")) {
			sb.append(" extends ").append(superClassName);
		}
		sb.append(" {\n\n");
		if (t.getName().equals("base")) {
			sb.append("\tprotected Element element;\n");
			sb.append("\tprotected Properties properties;\n");
			sb.append("\tprotected StringBuilder returningString = new StringBuilder();\n");
		}
		sb.append(referencesDeclarations(t));
		sb.append('\n');
		sb.append("\tpublic ").append(className)
				.append("(Properties properties) {\n");
		if (t.getName().equals("base")) {
			sb.append("\t\tthis.properties = properties;\n");
		} else {
			sb.append("\t\tsuper(properties);\n");
			sb.append(referencesDefinitions(t));
		}
		sb.append("\t}\n\n");

		return sb.toString();
	}
	
	private String isProcessableMethod(Template t) {
		StringBuilder sb = new StringBuilder();
		sb.append("\tpublic boolean isProcessable() {\n");
		if (t.getPrecondition() != null) {
			sb.append("\t\treturn ").append(t.getPrecondition()).append(";\n");
		} else {
			sb.append("\t\treturn true;\n");
		}
		sb.append("\t}\n\n");
		return sb.toString();
	}
	
	private String createFileMethod(Template t) {
		StringBuilder sb = new StringBuilder();
		if (t.getName().equals("base")) {
			sb.append("\tpublic void createFile() {\n");
			sb.append("\t}\n\n");
		}
		return sb.toString();
	}

	private List<String> dependencies(Template t) {
		List<String> ret = new ArrayList<String>();
		if (t.getSnippet() == null) {
			return ret;
		}
		ret.add(t.getExtends());
		SnippetType st = t.getSnippet();
		for (Serializable s : st.getContent()) {
			if (s instanceof JAXBElement) {
				JAXBElement<?> el = (JAXBElement<?>) s;
				String value = (String) el.getValue();
				if (!StringUtils.isMethod(value)) {
					ret.add(value);
				}
			}
		}
		return ret;
	}

	private String referencesDeclarations(Template t) {
		if (t.getSnippet() == null) {
			return "";
		}
		StringBuilder sb = new StringBuilder();
		SnippetType st = t.getSnippet();
		for (Serializable s : st.getContent()) {
			if (s instanceof JAXBElement) {
				JAXBElement<?> el = (JAXBElement<?>) s;
				String value = (String) el.getValue();
				if (!StringUtils.isMethod(value)) {
					String className = StringUtils
							.templateNameToClassName(value) + "TemplateClass";
					String refName = StringUtils.firstLower(className);
					sb.append("\tprotected ");
					sb.append(className);
					sb.append(" ");
					sb.append(refName);
					sb.append(";\n");
				}
			}
		}
		return sb.toString();
	}

	private String referencesDefinitions(Template t) {
		SnippetType st = t.getSnippet();
		if (t.getSnippet() == null) {
			return "";
		}
		StringBuilder sb = new StringBuilder();
		for (Serializable s : st.getContent()) {
			if (s instanceof JAXBElement) {
				JAXBElement<?> el = (JAXBElement<?>) s;
				String value = (String) el.getValue();
				if (!StringUtils.isMethod(value)) {
					String className = StringUtils
							.templateNameToClassName(value) + "TemplateClass";
					String refName = StringUtils.firstLower(className);
					sb.append("\t\t");
					sb.append(refName).append(" = new ").append(className)
							.append("(properties);\n");
				}
			}
		}
		return sb.toString();
	}

	private String elementSetter(Template t) {
		StringBuilder sb = new StringBuilder();
		sb.append("\tpublic void setElement(Element ele) {\n");
		sb.append("\t\tthis.element = ele;\n");
		sb.append("\t\treturningString = new StringBuilder();\n");
		SnippetType st = t.getSnippet();
		if (t.getSnippet() != null) {
			for (Serializable s : st.getContent()) {
				if (s instanceof JAXBElement) {
					JAXBElement<?> el = (JAXBElement<?>) s;
					String value = (String) el.getValue();
					if (!StringUtils.isMethod(value)) {
						String className = StringUtils
								.templateNameToClassName(value)
								+ "TemplateClass";
						String refName = StringUtils.firstLower(className);
						sb.append("\t\t");
						sb.append(refName).append(".setElement(ele);\n");
					}
				}
			}
		}
		sb.append("\t}\n\n");
		return sb.toString();
	}

	/**
	 * Generates the returning method java code for the template specified.
	 * 
	 * @param t
	 * @return
	 */
	private String returningMethod(Template t) {
		StringBuilder sb = new StringBuilder();
		if (t.getName().equals("base")) {
			sb.append("\tpublic String returning() {\n");
			sb.append("\t\treturn _returning();\n");
			sb.append("\t}\n\n");
			sb.append("\tprotected abstract String _returning();\n");
		} else {
			SnippetType st = t.getSnippet();
			sb.append("\tprotected String _returning() {\n");
			for (Serializable s : st.getContent()) {
				String str;
				if (s instanceof String) {
					str = "\t\treturningString.append(\""
							+ StringUtils.stringToCode((String) s) + "\");\n";
				} else if (s instanceof JAXBElement) {
					JAXBElement<?> el = (JAXBElement<?>) s;
					String value = (String) el.getValue();
					if (StringUtils.isMethod(value)) {
						str = "\t\treturningString.append(" + value + ");\n";
					} else {
						String className = StringUtils
								.templateNameToClassName(value)
								+ "TemplateClass";
						String refName = StringUtils.firstLower(className);
						str = "\t\tif (" + refName + ".isProcessable()) {\n";
						str += "\t\t\treturningString.append(" + refName + ".returning());\n";
						str += "\t\t}\n";
					}
				} else {
					str = "";
				}
				sb.append(str);
			}
			sb.append("\n\t\treturn returningString.toString();\n");
			sb.append("\t}");
		}
		return sb.toString();
	}

	private String definedMethods(Template t) {
		if (t.getJavaCode() != null) {
			return t.getJavaCode();
		} else {
			return "";
		}
	}
}
